package cc.shacocloud.mirage.restful.bind.support;

import cc.shacocloud.mirage.restful.HttpRequest;
import cc.shacocloud.mirage.restful.bind.annotation.FormAttribute;
import cc.shacocloud.mirage.restful.bind.annotation.QueryParam;
import cc.shacocloud.mirage.utils.ClassUtil;
import cc.shacocloud.mirage.utils.MethodParameter;
import cc.shacocloud.mirage.utils.Utils;
import cc.shacocloud.mirage.utils.collection.CollUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.vertx.core.Future;
import io.vertx.core.MultiMap;
import io.vertx.core.json.jackson.DatabindCodec;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 解析{@link QueryParam}或{@link FormAttribute}注解的方法参数
 * <p>
 * 支持三种方法参数定义类型：
 * <li>MultiMap类型，即当前类型注解的所有值</li>
 * <li>List类型|简单类型({@link ClassUtil#isSimpleValueType})数组，即当前类型注解指定名称的所有值</li>
 * <li>简单类型({@link ClassUtil#isSimpleValueType})，即当前类型注解指定名称的第一个值</li>
 * <li>其他，使用{@link DatabindCodec#mapper()} 的 {@link ObjectMapper#convertValue} 方法序列化</li>
 * <p>
 * 注意：{@link FormAttribute}注解表示的是表单数据，表单数据是从请求体中获取的，
 * 按照语义来说它应该被封装在{@link RequestResponseBodyMethodProcessor}中，但是因为该类型的特殊性，所以在这里做的特殊处理。
 */
public class QueryParamsAndFormAttributesMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
    
    private final ObjectMapper objectMapper;
    
    public QueryParamsAndFormAttributesMethodArgumentResolver(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }
    
    @Override
    public boolean supportsParameter(@NotNull MethodParameter parameter) {
        return parameter.hasParameterAnnotation(QueryParam.class)
                || parameter.hasParameterAnnotation(FormAttribute.class);
    }
    
    @Override
    protected NamedValueInfo createNamedValueInfo(@NotNull MethodParameter parameter) {
        QueryParam queryParam = parameter.getParameterAnnotation(QueryParam.class);
        if (queryParam != null) return new QueryParamsAndFormAttributesNamedValueInfo(queryParam);
        
        FormAttribute formAttribute = parameter.getParameterAnnotation(FormAttribute.class);
        if (formAttribute != null) return new QueryParamsAndFormAttributesNamedValueInfo(formAttribute);
        
        throw new IllegalStateException("不支持处理当前方法，请先执行 supportsParameter 方法进行判断：" + Utils.methodDescription(parameter.getDeclaringClass(), parameter.getMethod()));
    }
    
    @Override
    protected Future<Object> resolveName(String name, @NotNull MethodParameter parameter, HttpRequest request) {
        Object arg;
        
        Class<?> parameterType = parameter.getParameterType();
        boolean isQueryParam = parameter.hasParameterAnnotation(QueryParam.class);
        
        // 多部分参数
        if (MultiMap.class.isAssignableFrom(parameterType)) {
            arg = isQueryParam ? request.queryParams() : request.formAttributes();
        }
        // 列表 或者 集合
        else if (List.class.isAssignableFrom(parameterType)
                || (parameterType.isArray() && ClassUtil.isSimpleValueType(parameterType.getComponentType()))) {
            arg = isQueryParam ? request.queryParams(name) : request.formAttributes(name);
        }
        // 简单类型
        else if (ClassUtil.isSimpleValueType(parameterType)) {
            arg = isQueryParam ? request.queryParam(name) : request.formAttribute(name);
        }
        // 其他类型
        else {
            MultiMap multiMap = isQueryParam ? request.queryParams() : request.formAttributes();
            Iterator<Map.Entry<String, String>> iterator = multiMap.iterator();
            
            Map<String, Object> params = new HashMap<>();
            while (iterator.hasNext()) {
                String key = iterator.next().getKey();
                List<String> values = multiMap.getAll(key);
                
                if (CollUtil.isNotEmpty(values)) {
                    params.put(key, values.size() == 1 ? values.get(0) : values);
                }
            }
            
            arg = objectMapper.convertValue(params, parameterType);
        }
        
        return Future.succeededFuture(arg);
    }
    
    private static class QueryParamsAndFormAttributesNamedValueInfo extends NamedValueInfo {
        
        public QueryParamsAndFormAttributesNamedValueInfo(@NotNull QueryParam annotation) {
            super(annotation.name(), annotation.required(), annotation.defaultValue());
        }
        
        public QueryParamsAndFormAttributesNamedValueInfo(@NotNull FormAttribute annotation) {
            super(annotation.name(), annotation.required(), annotation.defaultValue());
        }
    }
}
